package com.ldzg.blockwatcher.config.tron;

import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ldzg.blockwatcher.watcher.tron.TronBlackListWatcher;
import com.ldzg.blockwatcher.watcher.tron.dto.TronTransactionDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;

import static com.ldzg.blockwatcher.constants.TronConstants.ADDED_BLOCK_LIST_EVENT;
import static com.ldzg.blockwatcher.constants.TronConstants.TRANSFER_EVENT;

/**
 * @author zhangyinhang
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class TronGridClient {

    public static final String META = "meta";
    public static final String FINGERPRINT = "fingerprint";
    public static final String DATA = "data";
    private final TronProperties tronProperties;

    private static final String HEADER_API_KEY = "TRON-PRO-API-KEY";

    private final ObjectMapper objectMapper = new ObjectMapper();

    //每次查询条数
    private static final int LIMIT = 50;

    private static volatile WebClient webClient;

    private static final Lock LOCK = new ReentrantLock();

    public static WebClient tronWebClient() {
        if (Objects.isNull(TronGridClient.webClient)) {
            LOCK.lock();
            try {
                if (Objects.isNull(TronGridClient.webClient)) {
                    TronGridClient.webClient = createClient();
                }
            } finally {
                LOCK.unlock();
            }
        }
        return TronGridClient.webClient;
    }

    private static WebClient createClient() {
        ConnectionProvider provider = ConnectionProvider.builder("block-webclient")
                .maxConnections(500)
                .pendingAcquireMaxCount(-1)
                .build();

        HttpClient httpClient = HttpClient.create(provider)
                // 响应时间
                .responseTimeout(Duration.ofSeconds(30));

        return WebClient.builder()
                // 设置Base地址
                .baseUrl(SpringUtil.getBean(TronProperties.class).getTronGridUrl())
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                // header头
                .defaultHeaders(header -> {

                    String apiKey = Optional.ofNullable(SpringUtil.getBean(TronProperties.class).getApiKey())
                            .orElseThrow(() -> new IllegalArgumentException("请先配置Tron调用API Key"));

                    // API必须要配置, 不然接口会被限流, 容易拉取不到数据
                    header.set(HEADER_API_KEY, apiKey);

                    header.setAccept(List.of(MediaType.APPLICATION_JSON));
                    header.setContentType(MediaType.APPLICATION_JSON);
                })
                // 此处可以添加拦截器,添加一些参数信息
                .filter(((request, next) -> {
                    log.info("[调用第三方接口 - 请求] 请求方法:{} 请求路径:{}", request.method(), request.url());
                    return next.exchange(request);
                }))
                .build();
    }

    private void handler(String eventName, long prev, long next, Consumer<JSONObject> consumer) {
        String fingerprint = null;

        while (true) {
            String uri = createEventUri(eventName, prev, next, fingerprint);

            String response = tronWebClient().get()
                              .uri(uri)
                              .retrieve()
                              .bodyToMono(String.class)
                              .block();

            JSONObject root = JSONUtil.parseObj(response);

            JSONObject meta = root.getJSONObject(META);
            if (!meta.isEmpty()) {
                fingerprint = meta.getStr(FINGERPRINT);
            }

            JSONArray dataArray = root.getJSONArray(DATA);

            if (dataArray.isEmpty()) {
                break;
            }
            // 数据处理
            for (Object o : dataArray) {
                JSONObject entries = new JSONObject(o);
                consumer.accept(entries);
            }

        }
    }

    /**
     * 获取已经添加到黑名单的数据
     */
    public void queryAddedBlackListEvent(long prev, long next) {

        // 监听冻结事件
        handler(ADDED_BLOCK_LIST_EVENT, prev, next, (jsonobject) -> {
            TronTransactionDTO transactionDTO = JSONUtil.toBean(jsonobject, TronTransactionDTO.class);
            log.info("TRON监听到冻结事件,参数:{}", JSONUtil.toJsonStr(transactionDTO));

            TronBlackListWatcher.enqueue(transactionDTO);
        });
    }

    /**
     * 构建请求URI
     *
     * @param eventName   事件名称
     * @param prev        开始时间
     * @param next        结束时间
     * @param fingerprint 是否翻页
     * @return 请求URI
     */
    private String createEventUri(String eventName, long prev, long next, String fingerprint) {
        StringBuilder uri = new StringBuilder();

        uri.append("/v1/contracts/")
                .append(tronProperties.getUsdtContract())
                .append("/events?event_name=").append(eventName)
                // 只拉取已确认的冻结数据
                .append("&only_confirmed=").append(Boolean.TRUE)
                // 每次分页查询
                .append("&limit=").append(LIMIT)
                // 时间范围
                .append("&min_block_timestamp=").append(prev)
                .append("&max_block_timestamp=").append(next);

        if (StringUtils.isNotBlank(fingerprint)) {
            uri.append("&fingerprint=").append(fingerprint);
        }
        return uri.toString();
    }

    /**
     * 获取实时交易
     *
     * @param prev 开始时间
     * @param next 结束时间
     */
    public void getLatestTransfer(long prev, long next) {
        // 监听冻结事件
        handler(TRANSFER_EVENT, prev, next, (jsonobject) -> {
            TronTransactionDTO transactionDTO = JSONUtil.toBean(jsonobject, TronTransactionDTO.class);
            log.info("TRON监听实时交易事件,参数:{}", JSONUtil.toJsonStr(transactionDTO));

            TronBlackListWatcher.enqueue(transactionDTO);
        });
    }

}
